{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Задание 4 - Перенос обучения (transfer learning) и тонкая настройка (fine-tuning)\n",
    "\n",
    "Одной из важнейшних техник в тренировке сетей - использовать заранее натренированные веса на более общей задачи в качестве начальной точки, а потом \"дотренировать\" их на конкретной.\n",
    "\n",
    "Такой подход и убыстряет обучение, и позволяет тренировать эффективные модели на маленьких наборах данных.\n",
    "\n",
    "В этом упражнении мы натренируем классификатор, который отличает хотдоги от не хотдогов!  \n",
    "(более подробно - https://www.youtube.com/watch?v=ACmydtFDTGs)\n",
    "\n",
    "Это задание требует доступа к GPU, поэтому его можно выполнять либо на компьютере с GPU от NVidia, либо в [Google Colab](https://colab.research.google.com/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 71
    },
    "colab_type": "code",
    "id": "FcXBeP1O7cnY",
    "outputId": "2b081ee6-3006-47a5-8733-ea0c317bc78e"
   },
   "outputs": [],
   "source": [
    "import json\n",
    "import os\n",
    "import csv\n",
    "import urllib\n",
    "from io import BytesIO\n",
    "from PIL import Image\n",
    "\n",
    "from socket import timeout\n",
    "\n",
    "from google.colab import files\n",
    "\n",
    "!pip3 install -q torch torchvision\n",
    "!pip3 install -q Pillow==4.0.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сначала давайте скачаем данные с картинками. Это сделает код в следующей ячейке. Данные будут разделены на две части. На обучающей выборке, которая будет храниться в папке **train_kaggle**, мы будем строить наши модели, а на тестовой выборке **test_kaggle** будем предсказывать класс, к которому относится фотография (хотдог или нет).\n",
    "\n",
    "### Если вы в Google Colab!\n",
    "\n",
    "В нем можно запускать ноутбуки с доступом к GPU. Они не очень быстрые, зато бесплатные!\n",
    "Каждый ноутбук получает свой собственный environment c доступным диском итд.\n",
    "\n",
    "Через 90 минут отсуствия активности этот environment пропадает со всеми данными.\n",
    "Поэтому нам придется скачивать данные каждый раз."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 122
    },
    "colab_type": "code",
    "id": "ourBj07Arm3R",
    "outputId": "10b4ee22-fbaa-4e2f-e12d-7c3e4022f0c1"
   },
   "outputs": [],
   "source": [
    "# Download train data\n",
    "!wget \"https://storage.googleapis.com/dlcourse_ai/train.zip\"\n",
    "!unzip -q \"train.zip\"\n",
    "\n",
    "train_folder = \"train_kaggle/\"\n",
    "# Count number of files in the train folder, should be 4603\n",
    "print('Number of files in the train folder', len(os.listdir(train_folder)))\n",
    "\n",
    "# Download test data\n",
    "!wget \"https://storage.googleapis.com/dlcourse_ai/test.zip\"\n",
    "!unzip -q \"test.zip\"\n",
    "\n",
    "test_folder = \"test_kaggle/\"\n",
    "# Count number of files in the test folder, should be 1150\n",
    "print('Number of files in the test folder', len(os.listdir(test_folder)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "NNU-OD9O9ltP"
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "from torchvision import models\n",
    "from torch.utils.data import Dataset, SubsetRandomSampler\n",
    "from torchvision import transforms\n",
    "\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "device = torch.device(\"cuda:0\") # Let's make sure GPU is available!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Имплементируем свой Dataset для загрузки данных\n",
    "\n",
    "В этом задании мы реализуем свой собственный класс Dataset для загрузки данных. Его цель - загрузить данные с диска и выдать по ним тензор с входом сети, меткой и идентификатором картинки (так будет проще подготовить сабмит для kaggle на тестовых данных).\n",
    "\n",
    "Вот ссылка, где хорошо объясняется как это делать на примере: https://pytorch.org/tutorials/beginner/data_loading_tutorial.html\n",
    "\n",
    "Ваш Dataset должен в качестве количества сэмплов выдать количество файлов в папке и уметь выдавать кортеж из сэмпла, метки по индексу и названия файла.\n",
    "Если название файла начинается со слов 'frankfurter', 'chili-dog' или 'hotdog' - метка положительная. Иначе отрицательная (ноль).\n",
    "\n",
    "И не забудьте поддержать возможность трансформации входа (аргумент `transforms`), она нам понадобится!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 253
    },
    "colab_type": "code",
    "id": "bN2SPiJa9v5M",
    "outputId": "c5d5db7c-746c-41db-d146-30e09f7f7278"
   },
   "outputs": [],
   "source": [
    "class HotdogOrNotDataset(Dataset):\n",
    "    def __init__(self, folder, transform=None):\n",
    "        self.transform = transform\n",
    "        \n",
    "        # TODO: Your code here!\n",
    "        \n",
    "    def __len__(self):\n",
    "        raise Exception(\"Not implemented!\")\n",
    "    \n",
    "    def __getitem__(self, index):        \n",
    "        # TODO Implement getting item by index\n",
    "        # Hint: os.path.join is helpful!\n",
    "        raise Exception(\"Not implemented!\")\n",
    "        return img, y, img_id\n",
    "\n",
    "def visualize_samples(dataset, indices, title=None, count=10):\n",
    "    # visualize random 10 samples\n",
    "    plt.figure(figsize=(count*3,3))\n",
    "    display_indices = indices[:count]\n",
    "    if title:\n",
    "        plt.suptitle(\"%s %s/%s\" % (title, len(display_indices), len(indices)))        \n",
    "    for i, index in enumerate(display_indices):    \n",
    "        x, y, _ = dataset[index]\n",
    "        plt.subplot(1,count,i+1)\n",
    "        plt.title(\"Label: %s\" % y)\n",
    "        plt.imshow(x)\n",
    "        plt.grid(False)\n",
    "        plt.axis('off')   \n",
    "    \n",
    "orig_dataset = HotdogOrNotDataset(train_folder)\n",
    "indices = np.random.choice(np.arange(len(orig_dataset)), 7, replace=False)\n",
    "\n",
    "visualize_samples(orig_dataset, indices, \"Samples\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 253
    },
    "colab_type": "code",
    "id": "mQNsUvYm4_2V",
    "outputId": "bb771beb-38bd-40ce-a935-b27841f748ca"
   },
   "outputs": [],
   "source": [
    "# Let's make sure transforms work!\n",
    "dataset = HotdogOrNotDataset(train_folder, transform=transforms.RandomVerticalFlip(0.9))\n",
    "\n",
    "visualize_samples(dataset, indices, \"Samples with flip - a lot should be flipped!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Создаем Dataset для тренировки\n",
    "\n",
    "И разделяем его на train и validation.\n",
    "На train будем обучать модель, на validation проверять ее качество, а соревнование Kaggle In-Class проведем на фотографиях из папки test_kaggle."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "YAvkoRx-9FsP"
   },
   "outputs": [],
   "source": [
    "# First, lets load the dataset\n",
    "train_dataset = HotdogOrNotDataset(train_folder, \n",
    "                       transform=transforms.Compose([\n",
    "                           transforms.Resize((224, 224)),\n",
    "                           transforms.ToTensor(),\n",
    "                           # Use mean and std for pretrained models\n",
    "                           # https://pytorch.org/docs/stable/torchvision/models.html\n",
    "                           transforms.Normalize(mean=[0.485, 0.456, 0.406],\n",
    "                                 std=[0.229, 0.224, 0.225])                         \n",
    "                       ])\n",
    "                      )\n",
    "test_dataset = HotdogOrNotDataset(test_folder, \n",
    "                       transform=transforms.Compose([\n",
    "                           transforms.Resize((224, 224)),\n",
    "                           transforms.ToTensor(),\n",
    "                           # Use mean and std for pretrained models\n",
    "                           # https://pytorch.org/docs/stable/torchvision/models.html\n",
    "                           transforms.Normalize(mean=[0.485, 0.456, 0.406],\n",
    "                                 std=[0.229, 0.224, 0.225])                         \n",
    "                       ])\n",
    "                      )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "YRnr8CPg7Hli"
   },
   "outputs": [],
   "source": [
    "batch_size = 64\n",
    "\n",
    "data_size = len(dataset)\n",
    "validation_fraction = .2\n",
    "\n",
    "\n",
    "val_split = int(np.floor((validation_fraction) * data_size))\n",
    "indices = list(range(data_size))\n",
    "np.random.seed(42)\n",
    "np.random.shuffle(indices)\n",
    "\n",
    "val_indices, train_indices = indices[:val_split], indices[val_split:]\n",
    "\n",
    "train_sampler = SubsetRandomSampler(train_indices)\n",
    "val_sampler = SubsetRandomSampler(val_indices)\n",
    "\n",
    "train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, \n",
    "                                           sampler=train_sampler)\n",
    "val_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size,\n",
    "                                         sampler=val_sampler)\n",
    "# Notice that we create test data loader in a different way. We don't have the labels.\n",
    "test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Наши обычные функции для тренировки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "2ek3KVQK7hJ6"
   },
   "outputs": [],
   "source": [
    "def train_model(model, train_loader, val_loader, loss, optimizer, num_epochs):    \n",
    "    loss_history = []\n",
    "    train_history = []\n",
    "    val_history = []\n",
    "    for epoch in range(num_epochs):\n",
    "        model.train() # Enter train mode\n",
    "        \n",
    "        loss_accum = 0\n",
    "        correct_samples = 0\n",
    "        total_samples = 0\n",
    "        for i_step, (x, y,_) in enumerate(train_loader):\n",
    "          \n",
    "            x_gpu = x.to(device)\n",
    "            y_gpu = y.to(device)\n",
    "            prediction = model(x_gpu)    \n",
    "            loss_value = loss(prediction, y_gpu)\n",
    "            optimizer.zero_grad()\n",
    "            loss_value.backward()\n",
    "            optimizer.step()\n",
    "            \n",
    "            _, indices = torch.max(prediction, 1)\n",
    "            correct_samples += torch.sum(indices == y_gpu)\n",
    "            total_samples += y.shape[0]\n",
    "            \n",
    "            loss_accum += loss_value\n",
    "\n",
    "        ave_loss = loss_accum / i_step\n",
    "        train_accuracy = float(correct_samples) / total_samples\n",
    "        val_accuracy = compute_accuracy(model, val_loader)\n",
    "        \n",
    "        loss_history.append(float(ave_loss))\n",
    "        train_history.append(train_accuracy)\n",
    "        val_history.append(val_accuracy)\n",
    "        \n",
    "        print(\"Average loss: %f, Train accuracy: %f, Val accuracy: %f\" % (ave_loss, train_accuracy, val_accuracy))\n",
    "        \n",
    "    return loss_history, train_history, val_history\n",
    "        \n",
    "def compute_accuracy(model, loader):\n",
    "    \"\"\"\n",
    "    Computes accuracy on the dataset wrapped in a loader\n",
    "    \n",
    "    Returns: accuracy as a float value between 0 and 1\n",
    "    \"\"\"\n",
    "    model.eval() # Evaluation mode\n",
    "    # TODO: Copy implementation from previous assignment\n",
    "    # Don't forget to move the data to device before running it through the model!\n",
    "    raise Exception(\"Not implemented\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Использование заранее натренированной сети (pretrained network)\n",
    "\n",
    "Чаще всего в качестве заранее натренированной сети используется сеть, натренированная на данных ImageNet с 1M изображений и 1000 классами.\n",
    "\n",
    "PyTorch включает такие натренированные сети для различных архитектур (https://pytorch.org/docs/stable/torchvision/models.html)  \n",
    "Мы будем использовать ResNet18.\n",
    "\n",
    "Для начала посмотрим, что выдает уже натренированная сеть на наших картинках. То есть, посмотрим к какому из 1000 классов их отнесет сеть.\n",
    "\n",
    "Запустите модель на 10 случайных картинках из датасета и выведите их вместе с классами с наибольшей вероятностью.  \n",
    "В коде уже есть код, который формирует соответствие между индексами в выходном векторе и классами ImageNet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 51
    },
    "colab_type": "code",
    "id": "CnvXSmtyLAgz",
    "outputId": "ca961672-29d0-4055-fad2-3b4c74cd500a"
   },
   "outputs": [],
   "source": [
    "# Thanks to https://discuss.pytorch.org/t/imagenet-classes/4923/2\n",
    "def load_imagenet_classes():\n",
    "    classes_json = urllib.request.urlopen('https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json').read()\n",
    "    classes = json.loads(classes_json)\n",
    "    \n",
    "    # TODO: Process it to return dict of class index to name\n",
    "    return { int(k): v[-1] for k, v in classes.items()}\n",
    "    \n",
    "model = models.resnet18(pretrained=True)\n",
    "\n",
    "# TODO: Run this model on 10 random images of your dataset and visualize what it predicts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "6a-3a1ZFGEw_"
   },
   "source": [
    "# Перенос обучения (transfer learning) - тренировать только последний слой\n",
    "\n",
    "Существует несколько вариантов переноса обучения, мы попробуем основные.  \n",
    "Первый вариант - заменить последний слой на новый и тренировать только его, заморозив остальные."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 85
    },
    "colab_type": "code",
    "id": "jCWMUWmr7t5g",
    "outputId": "87b511e8-7ddf-4530-d218-1f03bf03cdb0"
   },
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "\n",
    "model = models.resnet18(pretrained=True)\n",
    "# TODO: Freeze all the layers of this model and add a new output layer\n",
    "# https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html\n",
    "\n",
    "parameters = None   # Fill the right thing here!\n",
    "\n",
    "loss = nn.CrossEntropyLoss()\n",
    "optimizer = optim.SGD( parameters, lr=0.001, momentum=0.9)\n",
    "loss_history, train_history, val_history = train_model(model, train_loader, val_loader, loss, optimizer, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "8dDH4WfaB2Il"
   },
   "source": [
    "# Перенос обучения (transfer learning) - тренировать всю модель\n",
    "\n",
    "Второй вариант - точно так же заменить последгний слой на новый и обучать всю модель целиком."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 102
    },
    "colab_type": "code",
    "id": "5ss0jilyvuOh",
    "outputId": "3170f126-2b7a-405a-b63b-ccd21f94c5c2"
   },
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "\n",
    "model = models.resnet18(pretrained=True)\n",
    "# TODO: Add a new output layer and train the whole model\n",
    "# https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html\n",
    "parameters = None   # Fill the right thing here!\n",
    "\n",
    "loss = nn.CrossEntropyLoss()\n",
    "optimizer = optim.SGD( parameters, lr=0.001, momentum=0.9)\n",
    "\n",
    "loss_history, train_history, val_history = train_model(model, train_loader, val_loader, loss, optimizer, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "meQt_vDCs9cc"
   },
   "source": [
    "# Перенос обучения (transfer learning) - разные скорости обучения для разных слоев\n",
    "\n",
    "И наконец последний вариант, который мы рассмотрим - использовать разные скорости обучения для новых и старых слоев"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 102
    },
    "colab_type": "code",
    "id": "evro9ksXGs9u",
    "outputId": "e4f5aca7-2e1b-4972-e061-fe9109fbeb1f"
   },
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "\n",
    "model = models.resnet18(pretrained=True)\n",
    "# TODO: Add a new output layer\n",
    "# Train new layer with learning speed 0.001 and old layers with 0.0001\n",
    "# https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html\n",
    "loss = nn.CrossEntropyLoss()\n",
    "\n",
    "optimizer = None # Hint - look into what PyTorch optimizers let you configure!\n",
    "loss_history, train_history, val_history = train_model(model_conv, train_loader, val_loader, loss, optimizer, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Визуализируем метрики и ошибки модели\n",
    "\n",
    "Попробуем посмотреть, где модель ошибается - визуализируем ложные срабатывания (false positives) и ложноотрицательные срабатывания (false negatives).\n",
    "\n",
    "Для этого мы прогоним модель через все примеры и сравним ее с истинными метками (ground truth)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "ieEzZUglJAUB"
   },
   "outputs": [],
   "source": [
    "from torch.utils.data.sampler import Sampler\n",
    "\n",
    "class SubsetSampler(Sampler):\n",
    "    r\"\"\"Samples elements with given indices sequentially\n",
    "\n",
    "    Arguments:\n",
    "        data_source (Dataset): dataset to sample from\n",
    "        indices (ndarray): indices of the samples to take\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, indices):\n",
    "        self.indices = indices\n",
    "\n",
    "    def __iter__(self):\n",
    "        return (self.indices[i] for i in range(len(self.indices)))\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.indices)\n",
    "    \n",
    "    \n",
    "def evaluate_model(model, dataset, indices):\n",
    "    \"\"\"\n",
    "    Computes predictions and ground truth labels for the indices of the dataset\n",
    "    \n",
    "    Returns: \n",
    "    predictions: np array of booleans of model predictions\n",
    "    grount_truth: np array of boolean of actual labels of the dataset\n",
    "    \"\"\"\n",
    "    model.eval() # Evaluation mode\n",
    "    \n",
    "    # TODO: Evaluate model on the list of indices and capture predictions\n",
    "    # and ground truth labels\n",
    "    # Hint: SubsetSampler above could be useful!\n",
    "    \n",
    "    raise Exception(\"Not implemented\")\n",
    "    \n",
    "    return predictions, ground_truth\n",
    "\n",
    "predictions, gt = evaluate_model(model_conv, train_dataset, val_indices)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "r0bcioK6JBDK"
   },
   "source": [
    "И теперь можно визуализировать false positives и false negatives."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 469
    },
    "colab_type": "code",
    "id": "WMmaPfdeKk9H",
    "outputId": "c162d02d-385c-4994-df8e-0844c2969b9f"
   },
   "outputs": [],
   "source": [
    "# TODO: Compute indices of the false positives on the validation set.\n",
    "# Note those have to be indices of the original dataset\n",
    "false_positive_indices = None\n",
    "visualize_samples(orig_dataset, false_positive_indices, \"False positives\")\n",
    "\n",
    "# TODO: Compute indices of the false negatives on the validation set.\n",
    "# Note those have to be indices of the original dataset\n",
    "false_negatives_indices = None\n",
    "visualize_samples(orig_dataset, false_negatives_indices, \"False negatives\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "JoDeVjN4HZSV",
    "outputId": "c43261ad-524b-4a5f-ba53-fdce19c9f840"
   },
   "outputs": [],
   "source": [
    "import sklearn.metrics as metrics\n",
    "def binary_classification_metrics(prediction, ground_truth):\n",
    "    # TODO: Implement this function!\n",
    "    # We did this already it in the assignment1\n",
    "    return precision, recall, f1\n",
    "\n",
    "precision, recall, f1 = binary_classification_metrics(predictions, gt)\n",
    "print(\"F1: %4.3f, P: %4.3f, R: %4.3f\" % (f1, precision, recall))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "u_O9qiYySvuj"
   },
   "source": [
    "# Что будет в конце вы уже поняли\n",
    "\n",
    "Натренируйте лучшую модель на основе `resnet18`, меняя только процесс тренировки.\n",
    "Выбирайте лучшую модель по F1 score.\n",
    "\n",
    "Как всегда, не забываем:\n",
    "- побольше агментаций!\n",
    "- перебор гиперпараметров\n",
    "- различные оптимизаторы\n",
    "- какие слои тюнить\n",
    "- learning rate annealing\n",
    "- на какой эпохе останавливаться\n",
    "\n",
    "Наша цель - довести F1 score на validation set до значения, большего **0.93**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "i6mhfdQ9K-N3"
   },
   "outputs": [],
   "source": [
    "# TODO: Train your best model!\n",
    "best_model = None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "Y6xExdw8JB1l",
    "outputId": "8cdf205e-14ad-4013-924a-449d06cc3eec"
   },
   "outputs": [],
   "source": [
    "# Let's check how it performs on validation set!\n",
    "predictions, ground_truth = evaluate_model(best_model, dataset, val_indices)\n",
    "precision, recall, f1 = binary_classification_metrics(predictions, ground_truth)\n",
    "print(\"F1: %4.3f, P: %4.3f, R: %4.3f\" % (precision, recall, f1))\n",
    "\n",
    "# TODO: Visualize training curve for the best model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Визуализируйте ошибки лучшей модели"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "BFUeNOm1VACr"
   },
   "outputs": [],
   "source": [
    "# TODO Visualize false positives and false negatives of the best model on the validation set"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Необязательное задание с большой звездочкой\n",
    "\n",
    "Поучавствуйте в Kaggle In-Class Hot Dog Recognition Challenge!  \n",
    "Это соревнование сделано специально для курса и в нем учавствуют только те, кто проходит курс.\n",
    "\n",
    "В нем участники соревнуются в качестве натренированных моделей, загружая на сайт предсказания своих моделей на тестовой выборке. Разметка тестовой выборке участникам недоступна.\n",
    "Более подробно о правилах соревнования ниже.\n",
    "\n",
    "Те, кто проходят курс лично, за высокое место в соревновании получат дополнительные баллы.\n",
    "\n",
    "Здесь уже можно использовать и другие базовые архитектуры кроме `resnet18`, и ансамбли, и другие трюки тренировки моделей.\n",
    "\n",
    "Вот ссылка на соревнование:\n",
    "https://www.kaggle.com/c/hotdogornot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "image_id = []\n",
    "predictions = []\n",
    "model.eval()\n",
    "for x,_,id_img in test_loader:\n",
    "    # TODO : Напишите код для предсказания меток (1 = есть хотдог, 0 = хотдога нет)\n",
    "    # Код должен возвратить список из id картинки и метку predictions\n",
    "    # image id - это название файла картинки, например '10000.jpg'\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Так можно создать csv файл, чтобы затем загрузить его на kaggle\n",
    "# Ожидаемый формат csv-файла:\n",
    "# image_id,label\n",
    "# 10000.jpg,1\n",
    "# 10001.jpg,1\n",
    "# 10002.jpg,0\n",
    "# 10003.jpg,1\n",
    "# 10004.jpg,0\n",
    "\n",
    "with open('subm.csv', 'w') as submissionFile:\n",
    "    writer = csv.writer(submissionFile)\n",
    "    writer.writerow(['image_id', 'label'])\n",
    "    writer.writerows(zip(image_id,predictions))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# А так можно скачать файл с Google Colab\n",
    "files.download('subm.csv') "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Небольшое введение в Kaggle для тех, кто не слышал об этой платформе раньше"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В основе своей Kaggle - это платформа для проведения соревнований по машинному обучению. Появилась она в 2010 и, пожалуй, стала самой популярной и известной из всех существующих площадок по машинному обучению. Надо сказать, что Kaggle - это не только соревнования, но и сообщество людей, увлеченных машинным обучением. А судя по Википедии, в 2017 году отметка зарегистрированных пользователей перевалила за миллион. Есть там и обучающие материалы, возможность задавать вопросы, делиться кодом и идеями - просто мечта. \n",
    "\n",
    "### Как проходят соревнования? \n",
    "Обычно участники скачивают данные для обучения моделей (train data), чтобы затем делать предсказания на тестовых данных (test data). Обучающая выборка содержит как сами данные, так и правильные метки (значения зависимой переменной), чтобы можно было обучить модель. Но тестовые данные ответа не содержат - и нашей целью является предсказание меток по имеющимся данным. Файл с ответами для каждого наблюдения из тестовой выборки загружается на Kaggle и оценивается в соответствии с выбранной метрикой соревнования, а результат является публичным и показывается в общей таблице (ее называют еще лидербордом - leaderboard) - чтобы появилось желание посоревноваться и создать еще более сильную модель. В \"настоящих\" соревнованиях, которые проходят на Kaggle, есть и денежное вознаграждение для тех участников, кто занимает первые места на лидерборде. Например, в [этом](https://www.kaggle.com/c/zillow-prize-1#description) соревновании, человек, занявший первое место, получил около 1 000 000 долларов. \n",
    "\n",
    "Тестовые данные делятся случайным образом в некоторой пропорции. И пока соревнование идет, на лидерборде показываются очки и рейтинг участников только по одной части (Public Leaderboard). А вот когда соревнование заканчивается, то рейтинг участников составляется по второй части тестовых данных (Private Leaderboard). И часто можно видеть, как люди занимавшие первые места на публичной части тестовых данных, оказываются далеко не первыми на закрытой части тестовых данных. Зачем это сделано? Есть несколько причин, но, пожалуй, самой фундаментальной является идея недообучения-переобучения. Всегда возможно, что наша модель настроилась на конкретную выборку, но как она поведет себя на тех данных, которые еще не видела? Разбиение тестовых данных на публичную и скрытую части сделано для того, чтобы отобрать модели, которые имеют большую обобщающую способность. Одним из лозунгов участников соревнований стал \"Доверяйте своей локальной кросс-валидации\" (Trust your CV!). Есть очень большой соблазн оценивать свою модель по публичной части лидерборда, но лучшей стратегией будет выбирать ту модель, которая дает лучшую метрику на кросс-валидации на обучающей выборке. \n",
    "\n",
    "В нашем соревновании публичная часть лидерборда составляет 30%, а скрытая 70%. Вы можете делать до двух попыток в день, а оцениваться попытки будут по F1-мере. Удачи и доверяйте своей локальной валидации! В конце соревнования у вас будет возможность выбрать 2 из всех совершенных попыток - лучшая из этих двух и будет засчитана вам на скрытой части тестовых данных."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "collapsed_sections": [],
   "name": "HotdogOrNot.ipynb",
   "provenance": [],
   "version": "0.3.2"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
